package com.lin.filegraph.utils.model;


import com.lin.filegraph.utils.compdg.ComponentGraph;
import com.lin.filegraph.utils.compdg.ComponentNode;
import com.lin.filegraph.utils.filedg.FileGraph;
import com.lin.filegraph.utils.filedg.FileGraphNode;
import com.lin.filegraph.utils.filedg.FileInfo;
import com.lin.filegraph.utils.parser.node.JFileNode;
import com.lin.filegraph.utils.parser.node.JMethodNode;
import com.lin.filegraph.utils.parser.node.JParameterNode;
import com.lin.filegraph.utils.parser.node.JProjectNode;
import com.lin.filegraph.utils.parser.simpleast.SimpleASTHelper;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * @description:
 * @author: linhuaixu
 * @time: 2023/5/18 11:00
 */
public class ComponentInfo {

    private ComponentNode component;
    private JProjectNode root;
    private int LOC = 0;
    private int validLOC = 0;
    private int cyclomaticComplexity = 0;
    private int outDegreeInCompDG = 0;
    private int inDegreeInCompDG = 0;
    private List<FileInfo> listOfFileInfo;
    private int numOfTypedef = 0;
    private double cohesion;
    private int callDepth;
    private double coupling;
    private int methodCallDepth = -1;
    private int circleNumInSubFDG = 0;

    public ComponentInfo(ComponentNode component, JProjectNode root) {
        // TODO Auto-generated constructor stub
        this.component = component;
        this.root = root;
        listOfFileInfo = new ArrayList<>();
    }

    public void calculateCompInfo() {
        // TODO Auto-generated method stub
        FileGraph subFDG = getSubFDG(root.getFdg());
        circleNumInSubFDG = calculateCircleNumInFDG(subFDG);
        for (JFileNode file : SimpleASTHelper.getAllFileNodesInComp(component, root)) {
            FileInfo fileInfo = new FileInfo(file, component.getStrComponentName(), subFDG);
            fileInfo.calculateFileInfo();
            listOfFileInfo.add(fileInfo);
        }
        ComponentGraph compDG = root.getCompDG();
        outDegreeInCompDG = compDG.getPostComponents(component).size();
        inDegreeInCompDG = compDG.getPreComponents(component).size();
        for (FileInfo fileInfo : listOfFileInfo) {
            LOC += fileInfo.getLOC();
            validLOC += fileInfo.getValidLOC();
            cyclomaticComplexity += fileInfo.getCyclomaticComplexity();
            setNumOfTypedef(getNumOfTypedef() + fileInfo.getFileNode().getAllTypes().size());
        }
        int numOfAPIs = getAllAPIs().size();
        int numOfModules = component.getAllDirs().size();
        setCohesion(calculateCohesion(outDegreeInCompDG, inDegreeInCompDG, numOfAPIs, numOfModules));
//		setCompCallDepth();
        // int edgeNum = compDG.getAllComponentEdges().size();
        // int nodeNum = compDG.getAllComponents().size();
        setCoupling(calculateCoupling(outDegreeInCompDG, inDegreeInCompDG, listOfFileInfo.size()));
//		setMethodCallDepth(component.getMaxMethodCalldepth());
    }

    /**
     * @param subFDG
     * @return
     */
    public int calculateCircleNumInFDG(FileGraph subFDG) {
        // TODO Auto-generated method stub
        List<List<FileGraphNode>> loopList = new ArrayList<>();// 存储查找到的环
        Set<FileGraphNode> visitedNodes = new LinkedHashSet<>();// 存储被访问过的节点
        for (FileGraphNode node : subFDG.getListOfFiles()) {
            if (!visitedNodes.contains(node)) {
                List<FileGraphNode> trace = new ArrayList<>();// 存储当前访问路径
                findLoop(node, trace, loopList, visitedNodes, subFDG);
            }
        }
        return loopList.size();
    }

    private void findLoop(FileGraphNode node, List<FileGraphNode> trace, List<List<FileGraphNode>> loopList,
                          Set<FileGraphNode> visitedNodes, FileGraph subFDG) {

        if (trace.indexOf(node) != -1) {
            List<FileGraphNode> oneLoop = new ArrayList<>(trace.subList(trace.indexOf(node), trace.size()));
            oneLoop.add(node);
            loopList.add(oneLoop);
            return;
        }
        trace.add(node);

        for (FileGraphNode nextNode : subFDG.getPostFiles(node)) {
            if (!visitedNodes.contains(nextNode) && !nextNode.equals(node)) {
                findLoop(nextNode, trace, loopList, visitedNodes, subFDG);
            }
        }
        trace.remove(trace.size() - 1);
        visitedNodes.add(node);
    }

    /**
     * @param outDegreeInCompDG
     * @param inDegreeInCompDG
     * @return
     */
    private double calculateCoupling(int outDegreeInCompDG, int inDegreeInCompDG, int numOfFiles) {
        // TODO Auto-generated method stub
        if (numOfFiles < 1) {
            return 0;
        }
        return (double) (outDegreeInCompDG + inDegreeInCompDG) / (double) (numOfFiles);
    }

    /**
     * @return call depth of component
     */

    private double calculateCohesion(int out, int in, int I, int N) {
        // TODO Auto-generated method stub
        if (N <= 1) {
            return (double) ((out + in + I)) / ((double) (1) / (double) 2);
        }
        return (double) ((out + in + I)) / ((double) (N * (N - 1)) / (double) 2);
    }

    public void displayCompInfo() {
        System.out.println("display component basic info ...");
        System.out.println(component);
        System.out.println("LOC: " + LOC);
        System.out.println("Cyclomatic Complexity: " + cyclomaticComplexity);
        System.out.println("outDegreeInCompDG: " + outDegreeInCompDG);
        System.out.println("inDegreeInCompDG: " + inDegreeInCompDG);
        System.out.println("num of APIs: " + getAllAPIs().size());
        System.out.println("num of APIs which have Comments: " + getAllCommentAPIs().size());
        System.out.println("num of API parameters: " + getAllAPIParameters().size());

        System.out.println("num of files: " + listOfFileInfo.size());
        System.out.println("list of files: ");
        for (FileInfo fileInfo : listOfFileInfo) {
            System.out.println(fileInfo.getFileNode());
        }
    }

    public ComponentNode getComponent() {
        return component;
    }

    public int getCyclomaticComplexity() {
        // TODO Auto-generated method stub
        return cyclomaticComplexity;
    }

    public int getLOC() {
        // TODO Auto-generated method stub
        return LOC;
    }

    public List<JMethodNode> getAllMethods() {
        return SimpleASTHelper.getAllMethodsInComp(component, root);
    }

    public List<JMethodNode> getAllAPIs() {
        List<JMethodNode> allAPIs = new ArrayList<>();
        for (JMethodNode method : getAllMethods()) {
            if (method.isAPI()) {
                allAPIs.add(method);
            }
        }
        return allAPIs;
    }

    public List<JParameterNode> getAllAPIParameters() {
        List<JParameterNode> allAPIParameters = new ArrayList<>();
        for (JMethodNode api : getAllAPIs()) {
            allAPIParameters.addAll(api.getAllParameters());
        }
        return allAPIParameters;
    }

    public List<JMethodNode> getAllCommentAPIs() {
        List<JMethodNode> allCommentAPIs = new ArrayList<>();
        for (JMethodNode api : getAllAPIs()) {
            if (api.isHasComment()) {
                allCommentAPIs.add(api);
            }
        }
        return allCommentAPIs;
    }

    public List<JMethodNode> getAllCommentMethods() {
        List<JMethodNode> allCommentMethods = new ArrayList<>();
        for (JMethodNode m : getAllMethods()) {
            if (m.isHasComment()) {
                allCommentMethods.add(m);
            }
        }
        return allCommentMethods;
    }

    public FileGraph getSubFDG(FileGraph fdg) {
        List<String> subFilePaths = component.getAllFiles();
        List<FileGraphNode> subFileNodes = new ArrayList<>();
        for(FileGraphNode fgNode : fdg.getListOfFiles()) {
            if(subFilePaths.contains(fgNode.getRelativePath())) {
                subFileNodes.add(fgNode);
            }
        }
        FileGraph subFDG = fdg.getSubFDG(subFileNodes);
        return subFDG;
    }

    public int getOutDegreeInCompDG() {
        return outDegreeInCompDG;
    }

    public int getInDegreeInCompDG() {
        return inDegreeInCompDG;
    }

    public List<FileInfo> getListOfFileInfo() {
        return listOfFileInfo;
    }

    /**
     * @return the numOfTypedef
     */
    public int getNumOfTypedef() {
        return numOfTypedef;
    }

    /**
     * @param numOfTypedef
     *            the numOfTypedef to set
     */
    public void setNumOfTypedef(int numOfTypedef) {
        this.numOfTypedef = numOfTypedef;
    }

    /**
     * @return the cohesion
     */
    public double getCohesion() {
        return cohesion;
    }

    /**
     * @param cohesion
     *            the cohesion to set
     */
    public void setCohesion(double cohesion) {
        this.cohesion = cohesion;
    }

    /**
     * @return the callDepth
     */
    public int getCallDepth() {
        return callDepth;
    }

    public void setCallDepth(int callDepth) {
        this.callDepth = callDepth;
    }


    /**
     * @return the coupling
     */
    public double getCoupling() {
        return coupling;
    }

    /**
     * @param coupling
     *            the coupling to set
     */
    public void setCoupling(double coupling) {
        this.coupling = coupling;
    }

    /**
     * @return the methodCallDepth
     */
    public int getMethodCallDepth() {
        return methodCallDepth;
    }

    /**
     * @param methodCallDepth
     *            the methodCallDepth to set
     */
    public void setMethodCallDepth(int methodCallDepth) {
        this.methodCallDepth = methodCallDepth;
    }

    /**
     * @return the circleNumInSubFDG
     */
    public int getCircleNumInSubFDG() {
        return circleNumInSubFDG;
    }

    public int getValidLOC() {
        return validLOC;
    }

    /**
     * @param circleNumInSubFDG
     *            the circleNumInSubFDG to set
     */
    public void setCircleNumInSubFDG(int circleNumInSubFDG) {
        this.circleNumInSubFDG = circleNumInSubFDG;
    }

}
